#region Copyright Notice
// 
// 
//  
//  Copyright (c) 2009-2011 Anindya Chatterjee
//  
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// 
// 
#endregion
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using AbstractClass.Collections.Generic;

namespace QuasarTest
{
    /// <summary>
    ///   Algorithm - http://tech.einaregilsson.com/2007/09/20/binary-tree-image/
    /// </summary>
    /// <typeparam name = "T"></typeparam>
    public class BinaryTreeDrawer<T>
    {
        #region Constants

        private const int NodeWidth = 35;
        private const int HalfNodeWidth = NodeWidth / 2;
        private const int DX = 10;
        private const int DY = 10;
        private const int FontSize = 10;
        private const string FontName = "Arial";
        private const int Padding = 20;

        private readonly Brush Background = Brushes.White;
        private readonly Pen MainPen = Pens.Blue;
        private readonly Brush NodeBrush = Brushes.HotPink;
        private readonly Brush TextBrush = Brushes.White;
        private readonly Font TextFont = new Font(FontName, FontSize);

        #endregion

        #region Members

        private Bitmap _bitmap;
        private Graphics _graphics;
        private int _treeDepth;

        #endregion

        #region Image Creation

        public Bitmap DrawImage(BinaryTreeNode<T> root)
        {
            int canvasWidth, canvasHeight;
            ImageNode iroot = CreateImageNodeTree(root, 1);
            if (iroot == null)
                canvasHeight = canvasWidth = 30;
            else
            {
                canvasWidth = iroot.TotalWidth + Padding * 2;
                canvasHeight = _treeDepth * (NodeWidth + DY) + 1 * NodeWidth;
            }

            _bitmap = new Bitmap(canvasWidth, canvasHeight);
            _graphics = Graphics.FromImage(_bitmap);
            _graphics.SmoothingMode = SmoothingMode.AntiAlias;
            _graphics.FillRectangle(Background, 0, 0, _bitmap.Width, _bitmap.Height);
            DrawNode(iroot, true, 1, new Rectangle(0 - HalfNodeWidth, 0, NodeWidth, NodeWidth));
            return _bitmap;
        }

        private ImageNode CreateImageNodeTree(BinaryTreeNode<T> node, int depth)
        {
            if (node == null)
                return null;

            _treeDepth = Math.Max(_treeDepth, depth);

            ImageNode left = CreateImageNodeTree(node.Left, depth + 1);
            ImageNode right = CreateImageNodeTree(node.Right, depth + 1);
            var newNode = new ImageNode
                              {
                                  Node = node,
                                  Left = left,
                                  Right = right,
                                  RightTreeWidth = (right == null) ? 0 : right.TotalWidth,
                                  LeftTreeWidth = (left == null) ? 0 : left.TotalWidth
                              };

            newNode.TotalWidth = newNode.RightTreeWidth + newNode.LeftTreeWidth + NodeWidth + DX;

            return newNode;
        }

        #endregion

        #region Draw Nodes

        private Rectangle DrawNode(ImageNode node, bool isRightChild, int depth, Rectangle parentBounds)
        {
            if (node == null) return new Rectangle();

            int offset = (isRightChild) ? node.LeftTreeWidth + NodeWidth + DX : -node.RightTreeWidth - NodeWidth - DX;
            int x = parentBounds.X + offset;
            int y = depth * (DY + NodeWidth) - HalfNodeWidth;

            var bounds = new Rectangle(x, y, NodeWidth, NodeWidth);

            _graphics.FillEllipse(NodeBrush, bounds);
            DrawNodeText(node, bounds);

            if (node.Left != null)
            {
                Rectangle leftBounds = DrawNode(node.Left, false, depth + 1, bounds);
                _graphics.DrawLine(MainPen, leftBounds.X + HalfNodeWidth, leftBounds.Y, bounds.X + HalfNodeWidth,
                                   bounds.Y + NodeWidth);
            }
            if (node.Right != null)
            {
                Rectangle rightBounds = DrawNode(node.Right, true, depth + 1, bounds);
                _graphics.DrawLine(MainPen, rightBounds.X + HalfNodeWidth, rightBounds.Y, bounds.X + HalfNodeWidth,
                                   bounds.Y + NodeWidth);
            }

            return bounds;
        }

        private void DrawNodeText(ImageNode node, Rectangle nodeBounds)
        {
            var strFormat = new StringFormat {Alignment = StringAlignment.Center};
            _graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
            var typeBounds = new RectangleF(nodeBounds.X - 50, nodeBounds.Y + TextFont.GetHeight() / 2, 100 + NodeWidth,
                                            TextFont.GetHeight());
            _graphics.DrawString(node.Node.Value.ToString(), TextFont, TextBrush, typeBounds, strFormat);
        }

        #endregion

        #region Nested type: ImageNode

        private class ImageNode
        {
            public ImageNode Left;
            public int LeftTreeWidth;
            public BinaryTreeNode<T> Node;
            public ImageNode Right;
            public int RightTreeWidth;
            public int TotalWidth;
        }

        #endregion
    }
}